Research
Security News
Quasar RAT Disguised as an npm Package for Detecting Vulnerabilities in Ethereum Smart Contracts
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
@equinor/fusion-query
Advanced tools
The primary use case for Query
involves:
Using a Query
mechanism offers numerous benefits, including:
Query
libraries often come with features to automatically refetch data on certain triggers (e.g., window focus), ensuring the UI is always up-to-date with the latest server state without manual intervention.Query
implementations are usually highly customizable, allowing developers to tailor their behavior for specific needs, such as custom caching strategies, query deduplication, and more.When setting up a Query
, you can typically configure it with several options to tailor its behavior to your application's specific needs. While the exact options available can vary depending on the implementation of the Query
, common configuration parameters often include:
fn
(Function):
fetch
, Axios, or any other HTTP client.Retry Strategy:
Caching Strategy:
expire
: Time in milliseconds after which a cached item is considered stale.Concurrent Request Handling (Queuing Strategy):
switch
: Cancels any ongoing request when a new request comes in.merge
: Allows multiple requests to run in parallel.concat
: Queues requests and executes them sequentially.Logging / Debugging:
Request Transformation:
Response Transformation:
Error Handling:
signal
(AbortSignal):
AbortSignal
to requests, allowing you to cancel them programmatically if needed.Extended Configuration:
const queryClient = new QueryClient({
fn: async (args) => {
const response = await fetch(`https://your.api/${args.endpoint}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(args.params),
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
},
expire: 60000, // Cache data expires after 60 seconds
retry: { attempts: 3, delay: 1000 }, // Retry up to 3 times, 1 second apart
// Additional config options as necessary...
});
const args = {
/* your query arguments here */
};
query.query(args).subscribe({
next: (result) => console.log(result),
error: (error) => console.error(error),
complete: () => console.log('Query completed'),
});
const args = {
/* your query arguments here */
};
query
.queryAsync(args)
.then((result) => console.log(result))
.catch((error) => console.error(error));
const args = {
/* identify cache entry */
};
const changes = (prevState) => ({ ...prevState /* new state changes */ });
query.mutate(args, changes);
If you're confident about the new state of the data after the mutation, you can apply an optimistic update:
// Assume a function to update a post returns the updated post data
async function updatePost(postId, newData) {
const updatedPost = await apiUpdatePost(postId, newData); // perform API request to update the post
return updatedPost;
}
// Optimistically updating the cache with the new post data
fastQueryClient.mutate(['post', postId], async (oldData) => {
const updatedData = await updatePost(postId, newData);
return { ...oldData, ...updatedData, updated: Date.now() };
});
In this case, by providing the updated
attribute with the current timestamp, FastQuery knows that the cached data is fresh, preventing unnecessary refetches.
If the final state of the data after the mutation is uncertain or if it's preferable for the application to fetch fresh data from the server, you can choose to invalidate the cache item:
// Invalidate the cache item for the post, forcing a refetch next time
fastQueryClient.mutate(['post', postId], oldData => {
// Perform the mutation without directly updating the cache data
updatePost(postId, newData);
// Return null or undefined, or simply omit the updated attribute
// This marks the cache item as stale
return { ...oldData, updated: undefined }; // Omitting or setting undefined explicitly
});
Invalidate a specific cache entry or all:
query.invalidate(args); // Invalidates specific entry
query.invalidate(); // Invalidates all cache entries
Remember to unsubscribe from observables or to complete the query to release resources:
const subscription = query.query(args).subscribe((result) => console.log(result));
// Later, when you're done:
subscription.unsubscribe();
// Or, to complete and clean up the query itself:
query.complete();
When compiling code the FUSION_LOG_LEVEL
must be set (depending on build util, example shown for Vite)
Uncaught ReferenceError: process is not defined
defineConfig({
plugins: [
tsconfigPaths(),
viteEnv({
FUSION_LOG_LEVEL: process.env.FUSION_LOG_LEVEL ?? process.env.NODE_ENV === 'development' ? '3' : '1'
}),
]
})
## Advanced Usage
### Queue Operators
The Query utility allows you to manage concurrent requests using different queue strategies: `switch`, `merge`, and `concat`. Here's how you can apply each strategy:
#### Switch (Default)
Cancels the current active request when a new request comes in. Only the result from the latest request will be returned.
```typescript
import { Query } from '@equinor/fusion-query';
import { debounceTime, fromEvent, map, switchMap } from 'rxjs';
// Mock function to simulate data fetching based on the search query
async function fetchSearchResults(searchQuery: string) {
const response = await fetch(`https://your.api/search?query=${searchQuery}`);
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
}
// Define the query with the switch as a debounce strategy
const searchQuery = new Query<any, { searchQuery: string }>({
client: {
fn: ({ searchQuery }) => fetchSearchResults(searchQuery),
},
key: ({ searchQuery }) => `search-${searchQuery}`,
queueOperator: 'switch',
});
Now, set up the search input to listen for changes and use the defined query to fetch data:
const searchInput = document.getElementById('search-input');
fromEvent(searchInput, 'input')
.pipe(
map((event) => (event.target as HTMLInputElement).value),
debounceTime(300), // Debounce typing to limit queries
switchMap((searchQuery) => (searchQuery ? searchQuery.query({ searchQuery }) : [])),
)
.subscribe({
next: (results) => {
console.log('Search Results:', results);
// Handle rendering the search results here
},
error: (error) => console.error('Error fetching search results:', error),
});
Allows multiple requests to run in parallel without canceling each other. All responses will be returned as they arrive.
async function fetchData(endpoint: string, queryParams: object) {
const response = await fetch(`https://your.api/${endpoint}`, {
method: 'GET',
body: JSON.stringify(queryParams),
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
}
import { Query } from '@equinor/fusion-query';
import { combineLatest } from 'rxjs';
// Defining the query with `fn` function for client creation
const userProfileQuery = new Query<any, { endpoint: string; queryParams: object }>({
client: {
fn: fetchData,
},
key: (args) => `${args.endpoint}-${JSON.stringify(args.queryParams)}`,
queueOperator: 'merge', // Using merge to handle parallel queries
});
const userId = 'exampleUserId';
// Initiating parallel requests
const userDetails$ = userProfileQuery.query({ endpoint: 'users', queryParams: { userId } });
const userPosts$ = userProfileQuery.query({ endpoint: 'posts', queryParams: { userId } });
const userComments$ = userProfileQuery.query({ endpoint: 'comments', queryParams: { userId } });
// Combining the observables from parallel requests
combineLatest([userDetails$, userPosts$, userComments$]).subscribe({
next: ([userDetails, userPosts, userComments]) => {
// Handle and display the combined data as needed
console.log('User Details:', userDetails);
console.log('User Posts:', userPosts);
console.log('User Comments:', userComments);
},
error: (error) => console.error('Error fetching data:', error),
complete: () => console.log('All parallel queries completed'),
});
Queues requests and executes them one after another in a sequential manner. A new request will only start after the previous one has completed.
const query = new Query<YourDataType, YourArgsType>({
client,
key: (args) => JSON.stringify(args),
queueOperator: 'concat',
});
Cache validation is crucial for determining whether cached data is still relevant or needs to be refreshed. You can customize cache validation using the validate
option.
Automatically consider cache entries as stale after a specified duration.
const query = new Query<YourDataType, YourArgsType>({
client,
key: (args) => JSON.stringify(args),
expire: 60000, // 60 seconds
});
Implement a custom logic to validate cache entries based on your requirements.
const query = new Query<YourDataType, YourArgsType>({
client,
key: (args) => JSON.stringify(args),
validate: (entry, args) => {
// Your custom validation logic here.
// For example, return `false` if the entry is older than 30 minutes.
return Date.now() - entry.updated < 30 * 60 * 1000;
},
});
First, establish a shared QueryCache
instance and define a common function to fetch data. This shared cache will be utilized by different query instances across the application.
import { Query, QueryCache } from '@equinor/fusion-query';
// Initialize a shared QueryCache across the application
const sharedQueryCache = new QueryCache();
// Function to fetch blog posts data from your API
async function fetchBlogPosts() {
const response = await fetch('https://example.com/api/blog-posts');
if (!response.ok) {
throw new Error('Failed to fetch blog posts');
}
return response.json();
}
Next, directly initialize separate Query
instances in different parts of your application (e.g., for homepage posts and sidebar posts). These instances will share the same QueryCache
but are created independently.
// Create a Query instance for the homepage using the shared cache
const homepagePostsQuery = new Query({
client: {
fn: fetchBlogPosts,
},
cache: sharedQueryCache, // Utilize the shared cache
key: () => 'allBlogPosts', // Unique key for this query
});
// Fetch and render posts on the homepage
homepagePostsQuery.query().subscribe({
next: (posts) => {
// Render posts on the homepage
console.log('Homepage posts:', posts);
},
error: (error) => console.error('Error fetching posts for homepage:', error),
});
// Create a Query instance for the sidebar using the same shared cache
const sidebarPostsQuery = new Query({
client: {
fn: fetchBlogPosts,
},
cache: sharedQueryCache,
key: () => 'allBlogPosts', // Same key, leveraging cache from homepage query
});
// Fetch and display posts in the sidebar widget
sidebarPostsQuery.query().subscribe({
next: (posts) => {
// Render posts in the sidebar
console.log('Sidebar posts:', posts);
},
error: (error) => console.error('Error fetching posts for sidebar:', error),
});
Since both homepagePostsQuery
and sidebarPostsQuery
instances use the shared QueryCache
, fetching operations benefit from cache-first strategies, reducing unnecessary network requests.
"allBlogPosts"
data is fetched first by the homepage and then requested by the sidebar, the sidebar will immediately access the cached data without needing to fetch from the network again.FAQs
WIP
We found that @equinor/fusion-query demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
Security News
Research
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
Research
Security News
Socket researchers discovered a malware campaign on npm delivering the Skuld infostealer via typosquatted packages, exposing sensitive data.